/*
 * appender.cpp
 *
 *  Created on: 2008-11-11
 *      Author: fanliangliang
 */

#include "appender.h"
#include "foreach.h"
#include "thread.h"
#include "path.h"
#include "net/tcp_socket.h"
#include "net/udp_socket.h"
#include "net/byte_stream.h"

BEGIN_NAMESPACE_TOXIC

using namespace std;

static const char* levelToString(LogLevel level) {
	static const char* levelTable[] = {
		"TRC", "DBG", "INF", "WRN", "ERR", "FTL"
	};
	return levelTable[level];
}

static void defaultPrint(ostream& out, const LogEvent& e) {
	out << '[' << e.time << "][" << levelToString(e.level) << "]["
		<< toxic::basename(e.file) << ", " << e.line << ']' << e.message << endl;
}


ConsoleAppender::ConsoleAppender(ostream& out)
:_out(out) {
}

ConsoleAppender::~ConsoleAppender() {
}

void ConsoleAppender::doAppend(const LogEvent& e) {
	defaultPrint(_out, e);
}


FileAppender::FileAppender(const char* format,
	ios_base::openmode mode /*= ios_base::out | ios_base::trunc*/)
:_format(format)
,_mode(mode)
,_hasTid(_format.find("%t") != string::npos ? true : false) {

}

FileAppender::~FileAppender() {
	close();

	typedef TidAppenderMap::value_type value_type;
	foreach (const value_type& value, _map) {
		delete value.second;
	}
}

void FileAppender::doAppend(const LogEvent& e) {
	if (_hasTid) {
		TidAppenderMap::iterator it = _map.find(e.tid);
		if (it == _map.end()) {
			string format(_format);
			string tid = lexical_cast<string>(e.tid);
			replace(format, "%t", tid);
			FileAppender* ap = new FileAppender(format.c_str(), _mode);
			it = _map.insert(std::make_pair(e.tid, ap)).first;
		}
		it->second->doAppend(e);
	}
	else {
		string filename = e.time.toString(_format.c_str());
		if (_filename != filename) {
			_filename = filename;
			string root = toxic::dirname(_filename);
			toxic::createDirectory(root.c_str());
			close();
			_out.open(_filename.c_str(), _mode);
		}

		print(_out, e);
	}
}

void FileAppender::print(ostream& out, const LogEvent& e) {
	defaultPrint(out, e);
}

void FileAppender::close() {
	if (_out.is_open()) {
		_out.clear();
		_out.close();
	}
}


SocketAppender::SocketAppender(const string& protocol, const string& server, uint16_t port)
:_socket(0)
,_server(server)
,_port(port) {
	try {
		if (protocol == "tcp") {
			_socket = new TcpSocket();
		}
		else if (protocol == "udp") {
			_socket = new UdpSocket();
		}
		else {
			// TODO unknown protocol
		}

		if (_socket != 0) {
			_socket->connect(_server, _port);
		}
	}
	catch (const exception& e) {
		destory();
	}
}

SocketAppender::~SocketAppender() {
	destory();
}

void SocketAppender::destory() {
	delete _socket;
	_socket = 0;
}

void SocketAppender::doAppend(const LogEvent& e) {
	if (_socket != 0) {
		ByteStream bs;
		bs << uint32_t(0) << e;
		bs.seekp(0, ios::beg);
		bs << uint32_t(bs.size());
		try {
			_socket->sendN(bs.data(), bs.size());
		}
		catch (const exception& e) {
			destory();
		}
	}
}


ByteStream& operator<<(ByteStream& out, const LogEvent& e) {
	uint32_t time = e.time.time();
	uint8_t level = e.level;
	uint32_t tid = e.tid;
	uint32_t line = e.line;
	out << time << level << tid << line << e.file << e.message;
	return out;
}

ByteStream& operator>>(ByteStream& in, LogEvent& e) {
	uint32_t time;
	uint8_t level;
	uint32_t tid;
	uint32_t line;
	in >> time >> level >> tid >> line >> e.file >> e.message;
	e.time.setTime(time);
	e.level = LogLevel(level);
	e.tid = tid;
	e.line = line;
	return in;
}

END_NAMESPACE_TOXIC
